package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/ringbuf"
	"github.com/cilium/ebpf/rlimit"
	"github.com/spf13/cobra"
	"golang.org/x/sys/unix"
	"log"
	"os"
	"os/signal"
	"syscall"
)

const (
	symbol = "SSL_write_ex"
)

var (
	cmd = &cobra.Command{
		Use: "pysonar4go",
		Run: func(cmd *cobra.Command, args []string) {
			log.Printf("libssl path: %s\n", libssl_path)

			stopper := make(chan os.Signal, 1)
			signal.Notify(stopper, os.Interrupt, syscall.SIGTERM)
			signal.Notify(stopper, os.Interrupt, syscall.SIGINT)

			if err := rlimit.RemoveMemlock(); err != nil {
				log.Fatal(err)
			}

			objs := bpfObjects{}
			if err := loadBpfObjects(&objs, nil); err != nil {
				log.Fatalf("loading objects: %s", err)
			}
			defer objs.Close()

			// Open an ELF binary and read its symbols.
			ex, err := link.OpenExecutable(libssl_path)
			if err != nil {
				log.Fatalf("opening executable: %s", err)
			}

			// Open a Uretprobe at the exit point of the symbol and attach
			// the pre-compiled eBPF program to it.
			up, err := ex.Uprobe(symbol, objs.UprobeSSL_writeEx, nil)
			if err != nil {
				log.Fatalf("creating uretprobe: %s", err)
			}
			defer up.Close()

			rd, err := ringbuf.NewReader(objs.Rb)
			if err != nil {
				log.Fatalf("opening ringbuf reader: %s", err)
			}
			defer rd.Close()

			go func() {
				<-stopper
				log.Println("Received signal, exiting program..")

				if err := rd.Close(); err != nil {
					log.Fatalf("closing ringbuf reader: %s", err)
				}
			}()

			log.Println("Waiting for events..")

			var event bpfEvent
			for {
				record, err := rd.Read()
				if err != nil {
					if errors.Is(err, ringbuf.ErrClosed) {
						log.Println("Received signal, exiting..")
						return
					}
					log.Printf("reading from reader: %s", err)
					continue
				}

				// Parse the ringbuf event entry into a bpfEvent structure.
				if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
					log.Printf("parsing ringbuf event: %s", err)
					continue
				}

				log.Printf("CMD [%s] PID [%d], UID [%d] =========>\n%s\n<=========\n", unix.ByteSliceToString(event.Comm[:]), event.Pid, event.Uid, unix.ByteSliceToString(event.Buf[:]))
			}
		},
	}
)
